package models;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import net.sf.oval.constraint.ValidateWithMethod;

import org.hibernate.annotations.Cascade;

import play.data.validation.InFuture;
import play.data.validation.Max;
import play.data.validation.Min;
import play.data.validation.Required;
import play.db.jpa.Model;

@Entity
public class Plan extends Model {    
	
	// Cuando el plan no tiene fecha hasta, se generan dietas hasta esta fecha limite.
	// Inicialmente es 31-12-2012
	private static final Date FECHA_HASTA_LIMITE_PLAN = new Date(Calendar.getInstance().getTime().getYear() + 1, Calendar.JANUARY, 1);
	
//	/*
//	 * Seria ideal ver esta distribucion con un nutricionista. Gus.
//	 * Para la asignación de alimentos a cada comida, deberíamos repartir las calorías en el porcentaje
//	 * del mapa this.porcentajeCaloriasPorTipoComida y asegurarnos de que las calorias de cada alimento
//	 * no supere este porcentaje.
//	 * Con esto vamos a tener asegurad o que la cantidad de calorias del plan de generacion manual no sea
//	 * superada por el maximo que el usuario ingresa en el campo caloriasDiarias
//	 * Gus.
//	 *
//	 * */
//	@Deprecated
//	@Transient
//	private final Map<TipoComida, Integer> porcentajeCaloriasPorTipoComida = new HashMap<TipoComida, Integer>() {{
//		put(TipoComida.DESAYUNO, 15);
//		put(TipoComida.COLACION1, 10);
//		put(TipoComida.ALMUERZO, 25);
//		put(TipoComida.MERIENDA, 15);
//		put(TipoComida.COLACION2, 10);
//		put(TipoComida.CENA, 25);
//	}};
//	
//	/*
//	 * Seria ideal ver esta distribucion con un nutricionista. Gus.
//	 * */
//	@Deprecated
//	@Transient
//	private final Map<TipoComida, List<TipoAlimento>> alimentosAplicables = new HashMap<TipoComida, List<TipoAlimento>>() {{
//		
//		put(TipoComida.DESAYUNO, Arrays.asList(TipoAlimento.CEREALES, TipoAlimento.INFUSIONES,
//												TipoAlimento.LECHE));
//		
//		put(TipoComida.COLACION1, Arrays.asList(TipoAlimento.INFUSIONES, TipoAlimento.LECHE));
//		
//		put(TipoComida.ALMUERZO, Arrays.asList(TipoAlimento.CERDO, TipoAlimento.LEGUMBRES,
//												TipoAlimento.PASTAS, TipoAlimento.PESCADO,
//												TipoAlimento.POLLO, TipoAlimento.VACA,
//												TipoAlimento.VEGETALES_FECULENTOS));
//		
//		put(TipoComida.MERIENDA, Arrays.asList(TipoAlimento.CEREALES, TipoAlimento.INFUSIONES,
//												TipoAlimento.LECHE));
//		
//		put(TipoComida.COLACION2, Arrays.asList(TipoAlimento.INFUSIONES, TipoAlimento.LECHE));
//		
//		put(TipoComida.CENA, Arrays.asList(TipoAlimento.CERDO, TipoAlimento.LEGUMBRES,
//											TipoAlimento.PASTAS, TipoAlimento.PESCADO,
//											TipoAlimento.POLLO, TipoAlimento.VACA,
//											TipoAlimento.VEGETALES_FECULENTOS));
//	}};
//	
//	public List<TipoAlimento> getTipoAlimentoPorComida(TipoComida tipoComida) {
//		return alimentosAplicables.get(tipoComida);
//	}
//	
	
	@Required
	private String nombre;
	
	@Temporal(TemporalType.DATE)
	@Required
	private Date fechaInicio;
	
	@Temporal(TemporalType.DATE)
	@InFuture
	@ValidateWithMethod(methodName="validarCronologiaFechas", parameterType=Date.class)
	private Date fechaFin;
	
	@Min(value=500)
	@Max(value=10000)
	private Integer caloriasDiarias = 2000;
	
	// no sé qué es este "detach", nunca lo usé, pero lo dejo asi porque funciona el delete de un plan cuando está activo
	@ManyToOne(cascade=CascadeType.DETACH)
	private Usuario usuario;
	
	@OneToMany(cascade=CascadeType.ALL)
	@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
	@ValidateWithMethod(methodName="validarPorcionesPorComida", parameterType=List.class, message="plan.sinPorciones")
	private List<ComidaPlan> comidasPlan = new ArrayList<ComidaPlan>();
	
	@Enumerated(EnumType.STRING)
	private EstadoPlan estado;
	
    @Enumerated(EnumType.STRING)
    @Required
    private TipoPlan tipo;
    
	@OneToMany(cascade=CascadeType.ALL)
	private List<Dieta> dietas = new ArrayList<Dieta>();
	
	public Plan(Usuario usuario) {
		this.setUsuario(usuario);
		this.usuario.getPlanes().add(this);
		this.estado = EstadoPlan.INACTIVO;
	}
	
	public Plan(Usuario usuario, TemplatePlan templatePlan) {
		
		this(usuario);
		
		this.setCaloriasDiarias(templatePlan.getCaloriasDiarias());
		this.setNombre(templatePlan.getNombre());
		this.setTipo(templatePlan.getTipo());
		
		for (ComidaPlan comidaPlanTemplate : templatePlan.getComidasPlan()) {
			this.agegarComidaPlan(new ComidaPlan(comidaPlanTemplate));
		}
		
	}
	
	// Este metodo es publico porque quizas lo tenga que llamar cuando se haga la creacion manual del plan
	private void agegarDieta(Dieta dieta) {
		this.dietas.add(dieta);
		dieta.setPlan(this);
	}
	
	public void agegarComidaPlan(ComidaPlan comidaPlan) {
		this.comidasPlan.add(comidaPlan);
	}
	
	public boolean validarCronologiaFechas(Date fechaFin) {
		return (fechaInicio != null && fechaFin != null) ? fechaInicio.before(fechaFin) : false;
	}
	
	public boolean validarPorcionesPorComida(List<ComidaPlan> comidasPlan) {
		
		for (ComidaPlan comidaPlan : comidasPlan) {
			if(comidaPlan.getPorciones().isEmpty()) {
				return false;
			}
		}
		
		return true;
	}
	
	public void desactivar() {
		
		Calendar fechaHoy = Calendar.getInstance();
		
		this.estado = EstadoPlan.INACTIVO;
		
		eliminarDietas(fechaHoy.getTime(), this.fechaFin != null ? this.fechaFin : FECHA_HASTA_LIMITE_PLAN);
	}
	
	public void activar() {
		this.estado = EstadoPlan.ACTIVO;
		generarDietas();
	}
	
	public void cambiarEstado() {
		if (EstadoPlan.ACTIVO.equals(this.estado)) {
			desactivar();
		} else {
			activar();
		}
	}
	
	public static List<Plan> findByFechaFin() {
		return find("fechaFin = ? AND estado = ?", new Date(), EstadoPlan.ACTIVO).fetch();
	}
	
	public static List<Plan> findByMismoUsuarioActiva(Long usuarioId) {
		return find("estado=? and usuario.id = ?",  EstadoPlan.ACTIVO, usuarioId).fetch();
	}
	
	/**
	 * Crea una dieta para cada día desde la fecha de inicio hasta la fecha de fin del plan
	 * */
	private List<Dieta> eliminarDietas(Date desde, Date hasta) {
		
		List<Dieta> toRemove = new ArrayList<Dieta>();
		
		for (Dieta dieta : this.getDietas()) {
			
			if (dieta.getFecha().before(hasta) && dieta.getFecha().after(desde)) {
				toRemove.add(dieta);
			}
		}
		
		this.dietas.removeAll(toRemove);
		
		return this.dietas;
	}

	/**
	 * Crea una dieta para cada día desde la fecha de inicio hasta la fecha de fin del plan
	 * 
	 * */
	private List<Dieta> generarDietas() {
		
		Calendar fecha = Calendar.getInstance();
		
		// genero desde la fecha de inicio o desde hoy (si el inicio ya paso)
		Date desde = this.fechaInicio.after(Calendar.getInstance().getTime()) ? this.fechaInicio : Calendar.getInstance().getTime();
		
		// genero dietas hasta fechaFin o 31-12-12 si fechaFin es null
		Date hasta = (this.fechaFin != null) ? this.fechaFin : FECHA_HASTA_LIMITE_PLAN;
		
		fecha.setTime(desde);
		
		while(fecha.getTime().before(hasta)) {
			
			Dieta dieta = new Dieta(this, fecha.getTime());
			this.agegarDieta(dieta);
			
			fecha.add(Calendar.DATE, 1); // avanzo 1 día el while por fecha
		}
		
		return this.dietas;
	}
	
	public List<Dieta> getDietas() {
		return dietas;
	}
	
	public void setDietas(List<Dieta> dietas) {
		this.dietas = dietas;
	}
	
	public List<ComidaPlan> getComidasPlan() {
		return comidasPlan;
	}
	
	public void setComidasPlan(List<ComidaPlan> comidasPlan) {
		this.comidasPlan = comidasPlan;
	}
	
	public EstadoPlan getEstado() {
		return estado;
	}

	public void setEstado(EstadoPlan estado) {
		this.estado = estado;
	}

	public String getNombre() {
		return nombre;
	}
	
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	public Date getFechaInicio() {
		return fechaInicio;
	}
	
	public void setFechaInicio(Date fechaInicio) {
		this.fechaInicio = fechaInicio;
	}
	
	public Date getFechaFin() {
		return fechaFin;
	}
	
	public void setFechaFin(Date fechaFin) {
		this.fechaFin = fechaFin;
	}
	
	public Integer getCaloriasDiarias() {
		return caloriasDiarias;
	}
	
	public void setCaloriasDiarias(Integer caloriasDiarias) {
		this.caloriasDiarias = caloriasDiarias;
	}
	
	public Usuario getUsuario() {
		return usuario;
	}
	
	public void setUsuario(Usuario usuario) {
		this.usuario = usuario;
	}
	
	public TipoPlan getTipo() {
		return tipo;
	}
	
	public void setTipo(TipoPlan tipo) {
		this.tipo = tipo;
	}
	

//	@Deprecated
//	public Double getPorcentajeCaloriasPorTipoComida(TipoComida tipoComida) {
//		return new Double(this.caloriasDiarias * porcentajeCaloriasPorTipoComida.get(tipoComida) / 100);
//	}
//	
}
